package pl.edu.agh.student.vrp;

import org.jgap.*;
import org.jgap.impl.IntegerGene;

import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: Krzysiek
 * Date: 11.12.11
 * Time: 20:28
 * To change this template use File | Settings | File Templates.
 */
public class InitialPopulationGenerator {
    private int populationSize;
    private int vehicleCount;
    private Configuration conf;
    private List<Destination> destinations;
    private Map<Integer, List<Integer>> customersBestNeighbours = new HashMap<Integer, List<Integer>>();
    private Set<Integer> allCustomerSet = new HashSet<Integer>();

    public InitialPopulationGenerator(List<Destination> destinations, int populationSize, int vehicleCount,
                                      Configuration conf) {
        this.destinations = destinations;
        this.populationSize = populationSize;
        this.vehicleCount = vehicleCount;
        this.conf = conf;
        for (Destination destination : destinations) {
            if (destination.getCustomerNo() == 0)
                continue;
            allCustomerSet.add(destination.getCustomerNo());
        }
        allCustomerSet.remove(new Integer(1));
        initCustomersBestNeighbours();
    }

    public IChromosome[] getInitialPopulation() throws InvalidConfigurationException {
        IChromosome[] chromosomes = new IChromosome[populationSize];

        sortByDistance();
        Set<Integer> customers;
        Random rand = new Random();
        for (int i = 0; i < populationSize; i++) {
            if (i == 2 * populationSize / 3 && i % 3 == 0)
                sortByTimeWindow();
//            int customersPerRoute = 5;
            int customersPerRoute = rand.nextInt(3) + 5;
            int vehiclesLeft = vehicleCount;
            customers = new HashSet<Integer>(allCustomerSet);
            Gene genes[] = new Gene[allCustomerSet.size() + vehicleCount];
            int currentGene = 0;
            while (customers.size() > 0) {
                Integer randomCustomer = (Integer) customers.toArray()[rand.nextInt(customers.size())];
                genes[currentGene] = new IntegerGene(conf, 1, allCustomerSet.size());
                genes[currentGene].setAllele(new Integer(randomCustomer.intValue() - 1));
                currentGene++;
                customers.remove(randomCustomer);

                List<Integer> neighbours = customersBestNeighbours.get(randomCustomer);
                int j = 0;
                int routeLength = 0;
                while (j < allCustomerSet.size() - 1 && routeLength < customersPerRoute && customers.size() > 0) {
                    if (customers.contains(neighbours.get(j))) {
                        genes[currentGene] = new IntegerGene(conf, 1, allCustomerSet.size());
                        genes[currentGene].setAllele(new Integer(neighbours.get(j).intValue() - 1));
                        customers.remove(neighbours.get(j));
                        currentGene++;
                        routeLength++;
                    }
                    j++;
                }

                if (vehiclesLeft > 0) {
                    genes[currentGene] = new IntegerGene(conf, 0, 0);
                    genes[currentGene].setAllele(new Integer(0));
                    currentGene++;
                    vehiclesLeft--;
                }
            }
            while (vehiclesLeft > 0) {
                genes[currentGene] = new IntegerGene(conf, 0, 0);
                genes[currentGene].setAllele(new Integer(0));
                currentGene++;
                vehiclesLeft--;
            }
            chromosomes[i] = new Chromosome(conf, genes);
        }

        return chromosomes;
    }

    private void sortByDistance() {
        for (Map.Entry<Integer, List<Integer>> entry : customersBestNeighbours.entrySet()) {
            final Integer currentCustomer = entry.getKey();
            List<Integer> neighbours = entry.getValue();
            Collections.sort(neighbours, new Comparator<Integer>() {
                public int compare(Integer customerA, Integer customerB) {
                    Destination dA = getCustomer(customerA);
                    Destination dB = getCustomer(customerB);
                    double currentCustomerToA = getCustomer(currentCustomer).distanceTo(dA);
                    double currentCustomerToB = getCustomer(currentCustomer).distanceTo(dB);
                    return Double.compare(currentCustomerToA, currentCustomerToB);
                }
            });
        }
    }

    private void sortByTimeWindow() {
        for (Map.Entry<Integer, List<Integer>> entry : customersBestNeighbours.entrySet()) {
            final Integer currentCustomer = entry.getKey();
            List<Integer> neighbours = entry.getValue();
            final Destination currentDest = getCustomer(currentCustomer);
            Collections.sort(neighbours, new Comparator<Integer>() {
                public int compare(Integer customerA, Integer customerB) {
                    Destination dA = getCustomer(customerA);
                    Destination dB = getCustomer(customerB);
                    double currentCustomerToA = dA.getReadyTime() - currentDest.getDueTime();
                    double currentCustomerToB = dB.getReadyTime() - currentDest.getDueTime();
                    double maxTime = Math.max(Math.abs(currentCustomerToA), Math.abs(currentCustomerToB));
                    currentCustomerToA = Math.abs(currentCustomerToA) + 2 * maxTime;
                    currentCustomerToB = Math.abs(currentCustomerToB) + 2 * maxTime;
                    return Double.compare(currentCustomerToA, currentCustomerToB);
                }
            });
        }
    }

    public Destination getCustomer(int i) {
        return destinations.get(i - 1);
    }

    private void initCustomersBestNeighbours() {
        customersBestNeighbours.clear();
        for (Destination destination : destinations) {
            if (destination.getCustomerNo() == 0)
                continue;
            List<Integer> neighbours = new ArrayList<Integer>(allCustomerSet);
            neighbours.remove(new Integer(destination.getCustomerNo()));
            customersBestNeighbours.put(destination.getCustomerNo(), neighbours);
        }
    }
}
